Skip to content

build: install libe3 like a regular library, publish the binding as libe3py (0.2.3) - #125

Merged
Thecave3 merged 4 commits into
mainfrom
fix/python-install-prefix
Oct 10, 2026
Merged

Thecave3 merged 4 commits into
mainfrom
fix/python-install-prefix

Conversation

@Thecave3

Copy link
Copy Markdown
Collaborator

Summary

  • Install like a regular library. ./build_libe3 --all-encodings --install puts libe3 into /usr/local with a single sudo, refreshes the linker cache, and writes nothing into any Python site-packages. Before, --install --enable-swig copied the Python module into the system dist-packages whenever the right venv was not active; without root that failed, and because the module's install rules ran before the library's, it stopped the install before liblibe3, the headers and libe3.pc were written, so consumers then reported Package 'libe3' not found.
  • The Python binding is a pip package, libe3py, built against the installed libe3 (find_package(libe3 CONFIG), static link as before, same feature macros) and refusing an installed libe3 of another version. Every release now publishes its sdist to PyPI and then TestPyPI (trusted publishing, environments pypi / testpypi, as the dApp library does) and attaches it to the GitHub release. The dApp library will depend on it, so pip install dapps brings it in.
  • build_libe3: --all-encodings (ASN.1, JSON, Protocol Buffers), --install uses sudo only when the prefix is not writable, and -I now works on a machine without CMake (the CMake check ran before the dependencies were installed). VERSION 0.2.2 -> 0.2.3.

Type of change

  • Bug fix
  • New feature / enhancement
  • New Service Model
  • Refactor (no behavior change)
  • Documentation
  • Test / CI / packaging
  • Other (explain):

Linked issue

No tracking issue; this PR is the record of the change.

Mandatory test checklist

  • ./build_libe3 -c -d build -j $(nproc) -r -t passes (Release build + tests): left to CI
  • ./build_libe3 -c -d build -j $(nproc) -g -t passes (Debug build + tests): left to CI
  • cd build && ctest --output-on-failure is clean: left to CI
  • MPMC queue benchmark: not applicable, mpmc_queue.hpp is untouched
  • VERSION bumped per SemVer (0.2.3; no API or ABI change, packaging and install behaviour change)
  • Public headers under include/: none touched
  • New build dependencies are installed by ./build_libe3 -I (none added; -I itself fixed)
  • libe3.pc interface: unchanged; dApp-openairinterface5g configures and builds against the /usr/local install with no PKG_CONFIG_PATH (Found libe3, version 0.2.3, encodings asn1;json;protobuf)

Checked in a fresh ubuntu:24.04 container as a non-root user with sudo:

  • ./build_libe3 -I on a machine without CMake, then ./build_libe3 --all-encodings --install: one sudo, ldconfig run, pkg-config --modversion libe3 and find_package(libe3) work with no environment variable, nothing written to any Python site-packages;
  • python -m build --sdist, then pip install dist/libe3py-*.tar.gz in a venv without sudo: the module has no liblibe3 or libpython run-time dependency and tests/test_swig_smoke.py passes on it; pip install . from the checkout works; a package whose version differs from the installed libe3 is refused with the mismatch message;
  • the in-tree --enable-swig build passes ctest -L swig.

Also used end to end: the dApp library pip-installs libe3py 0.2.3 against it (its test suite passes, libe3py tests no longer skipped), and an OAI gNB built against it serves a dApp in an RF simulator setup with a UE.

scripts/check_commit_trailers.py --base origin/main --head HEAD is clean; actionlint reports nothing new on the two edited workflows.

Not checked: the publishing jobs (they only run on a release), the root install itself outside the container, macOS, and the Debian package build.

CI checklist

  • The report's verdict is green for the head commit
  • Unit Tests is green (Debug + Release matrix on ubuntu-latest)
  • Commit policy is green (trailers + linear history + each commit builds/tests independently)
  • MPMC Queue Benchmark: not applicable

Twin-repo coordination

  • This PR does not change the E3 wire protocol or public ABI.

Paired PR(s): the dApp library will add the libe3py>=0.2.3,<0.3 dependency once 0.2.3 is published; the OAI E3 image moves to 0.2.3 with --all-encodings in dApp-openairinterface5g.

Workflow confirmation

  • My branch is a linear, fast-forward-able descendant of main (rebased if main moved), with no merge commits.
  • Every commit builds and passes tests on its own (atomic, git bisect-safe): left to the Commit policy workflow; the head was tested as above.
  • I have read and followed CONTRIBUTING.md.

`build_libe3 --install --enable-swig` installed the Python module into the
site-packages of whatever interpreter CMake found, regardless of --prefix:
the system one unless the right venv was active. Without root that install
failed, and because the module's rules ran before the library's own, the
failure stopped the install before liblibe3, the headers and libe3.pc were
written; consumers then reported "Package 'libe3' not found".

A library install now writes only the library. `cmake --install` installs
the module only when -DLIBE3_PYTHON_INSTALL_DIR is given (CI's staged
install keeps working), and its rules are included after libe3Install, so
they cannot stop the library install. The binding is meant to be installed
with pip, built against the installed library (next commit). The SWIG module
definition moves to cmake/libe3SwigModule.cmake so the in-tree build and the
pip build compile it the same way; it links Python3::Module (not libpython)
when CMake has it.

build_libe3:
- --all-encodings enables ASN.1, JSON and Protocol Buffers, what a gNB needs
  to serve any dApp;
- --install uses sudo only when the prefix is not writable, refreshes the
  linker cache after an install into /usr or /usr/local, and prints the
  PKG_CONFIG_PATH line for any other prefix;
- the CMake check runs after --install-deps, which is what installs CMake on
  a fresh machine: before, `./build_libe3 -I` stopped with "CMake is
  required" on exactly the machine it is meant for.

The README gives one install recipe, the one OAI and the dApp library point
to.

Checked in a fresh ubuntu:24.04 container as a user with sudo: -I on a
machine without CMake, --all-encodings --install into /usr/local (one sudo,
ldconfig run), pkg-config and find_package find it with no environment
variable, nothing is written into any Python site-packages, and the in-tree
--enable-swig build passes `ctest -L swig`.

Assisted-by: Claude:claude-opus-5-5
…ibe3

The Python binding could only be had by building libe3 with SWIG and
installing it into the right interpreter. It is now a package, libe3py,
that pip builds against the libe3 installed on the machine, in any virtual
environment and without root:

  ./build_libe3 --all-encodings --install   # once, sudo
  pip install libe3py                       # or pip install . from a checkout

- pyproject.toml (scikit-build-core) takes its version from VERSION and
  builds python/CMakeLists.txt; the sdist carries only the SWIG sources, the
  shared module definition and the license files.
- python/CMakeLists.txt finds the installed library with
  find_package(libe3 CONFIG), so the module links libe3 statically as before,
  with the feature macros (encodings, latrec) libe3 was built with, and needs
  no liblibe3.so at run time. It stops unless the installed libe3 is the
  same version as the package, which keeps library and binding in step.
- tag-and-release builds the sdist on every release, publishes it to PyPI
  and then TestPyPI (trusted publishing, environments pypi and testpypi, as
  the dApp library does), and attaches it to the GitHub release. sdist only:
  a plain linux_x86_64 wheel is rejected by PyPI.
- pr-tests' swig job pip-installs the sdist against its staged install and
  runs the smoke test on the result.

Checked in a fresh ubuntu:24.04 container: the sdist builds; pip installs it
in a venv without sudo against libe3 in /usr/local; the module has no
liblibe3 or libpython run-time dependency; tests/test_swig_smoke.py passes
on it; `pip install .` from the checkout works; a package of another version
is refused with the mismatch message. actionlint reports nothing new on
either workflow. Not checked: the publishing jobs (they need a release), and
a libe3 outside the standard prefixes through CMAKE_PREFIX_PATH (CI covers it).

Assisted-by: Claude:claude-opus-5-5
libe3 is installed as a regular library and its Python binding is the libe3py
package on PyPI, built against it; build_libe3 -I works on a machine without
CMake.

Assisted-by: Claude:claude-opus-5-5
@Thecave3
Thecave3 force-pushed the fix/python-install-prefix branch from 9bbce95 to 3ce706c Compare October 10, 2026 15:03
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

CI report — 416c7d0 — ✅ all checks passed

Workflow Result Time Run
Commit policy ✅ success 2m32s #135
E2E dApp Integration ✅ success 1m58s #140
E2E Topologies (multi-dApp / multi-RAN) ✅ success 2m12s #138
Full-loop Latency Benchmark ✅ success 1m02s #139
Unit Tests ✅ success 4m22s #166
latrec portability ⏭️ not triggered (paths filter) — —
MPMC Queue Benchmark ⏭️ not triggered (paths filter) — —
E2E Topologies (multi-dApp / multi-RAN)

zmq/ipc

  • ✅ 1 RAN - 1 dApp: indications=5
    • dapp peer=t11 ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
  • ✅ 1 RAN - 2 dApps: dApp#1 ind=5 sub=1, dApp#2 ind=6 sub=2, RAN saw 2 dApps
    • dapp1 peer=t12 ran=ran-shared sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp2 peer=t12 ran=ran-shared sub=2 indications=6 seq=[0..5] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:6 2-5:0 6-10:0 >10:0]
  • ✅ 2 RANs - 1 dApp: from ran-a ind=5, from ran-b ind=5
    • dapp peer=t2a ran=ran-a sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp peer=t2b ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]

zmq/tcp

  • ✅ 1 RAN - 1 dApp: indications=5
    • dapp peer=default ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
  • ✅ 1 RAN - 2 dApps: dApp#1 ind=5 sub=1, dApp#2 ind=6 sub=2, RAN saw 2 dApps
    • dapp1 peer=default ran=ran-shared sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp2 peer=default ran=ran-shared sub=2 indications=6 seq=[0..5] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:6 2-5:0 6-10:0 >10:0]
  • ✅ 2 RANs - 1 dApp: from ran-a ind=5, from ran-b ind=5
    • dapp peer=default ran=ran-a sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.8 max=1 @seq=1) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp peer=off100 ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
E2E dApp Integration

✅ posix/ipc

  • dApp exit: 0
  • Indications received: 7

✅ posix/tcp

  • dApp exit: 0
  • Indications received: 7

✅ zmq/ipc

  • dApp exit: 0
  • Indications received: 7

✅ zmq/tcp

  • dApp exit: 0
  • Indications received: 7
Full-loop Latency Benchmark

Full-loop latency

Full-loop latency benchmark (N=1023 after 50 warmup)

All values in microseconds (μs). Link: zmq, transport: ipc, encoding: ASN.1 APER.

# Description Tags mean p50 p99 max
1 Collect indication data RECORD_BEGIN to ENCODE_E3SM_BEGIN 0.12 0.11 0.35 0.62
2 Create & encode indication ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE 1.02 1.00 1.43 19.49
3 Encode E3AP (indication) EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE 3.08 3.04 5.65 5.91
4 Queuing (indication) ENQUEUE to DEQUEUE 5.50 5.63 14.73 26.85
5 Delivery (indication) ENCODE_E3AP_DONE to SEND_DONE 0.32 0.27 0.56 8.60
6 E3 wire (RAN -> dApp) SEND_DONE to RECV 51.17 51.61 65.18 132.00
7 Decode E3AP (indication) RECV to DECODE_E3AP_DONE 1.76 1.50 5.74 11.19
8 libe3 dispatch (indication) DECODE_E3AP_DONE to DELIVER_BEGIN 0.12 0.13 0.17 0.22
9 Decode indication DELIVER_BEGIN to DECODE_E3SM_DONE 0.71 0.66 0.96 1.28
10 Process data DECODE_E3SM_DONE to ENCODE_E3SM_BEGIN 0.04 0.04 0.05 0.07
11 Create & encode control ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE 0.37 0.35 0.54 0.72
12 Encode E3AP (control) EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE 3.55 3.53 5.03 6.20
13 Queuing (control) ENQUEUE to DEQUEUE 16.06 15.94 25.94 283.50
14 Delivery (control) ENCODE_E3AP_DONE to SEND_DONE 5.28 5.14 8.16 10.18
15 E3 wire (dApp -> RAN) SEND_DONE to RECV 51.42 51.92 63.38 107.69
16 Decode E3AP (control) RECV to DECODE_E3AP_DONE 2.77 2.58 5.99 18.42
17 libe3 dispatch (control) DECODE_E3AP_DONE to DECODE_E3SM_BEGIN 0.26 0.26 0.39 1.90
18 Decode & handle control DECODE_E3SM_BEGIN to DECODE_E3SM_DONE 0.37 0.34 0.57 0.72
Total Total round-trip 144.42 144.65 178.25 451.50

ubuntu-latest, Release build, ZMQ + IPC, ASN.1 APER.

These numbers are measured inside a GitHub Actions container and should be treated as an upper bound on E3AP's and the library's own latency, not a representative deployment measurement.

Ready to merge (fast-forward only)

A maintainer can land the reviewed commits with:

git fetch origin
git checkout main && git merge --ff-only 416c7d079aebca79cc143030ce0d3e4c367c9628 && git push origin main

Head: 416c7d079aebca79cc143030ce0d3e4c367c9628 (branch fix/python-install-prefix). If --ff-only fails as non-fast-forward, the branch must be rebased on the latest main.

One comment per PR, rewritten in place once every workflow for 416c7d0 finished.

…cks to CI

The template asked contributors to tick a mandatory test checklist, a CI
checklist and a workflow confirmation. Every box restates something CI
already enforces and reports in the single CI report comment (Release and
Debug builds and tests, the benchmark, the commit policy: trailers, linear
history, per-commit builds, SPDX), so the boxes added work without adding a
check. Keep the summary, the type of change, the linked issue and the
twin-repo coordination, and say in CONTRIBUTING that CI is what checks.

Assisted-by: Claude:claude-opus-5-5
@Thecave3
Thecave3 merged commit 416c7d0 into main Oct 10, 2026
25 checks passed
@Thecave3
Thecave3 deleted the fix/python-install-prefix branch October 10, 2026 15:20

This branch was successfully deployed

2 active deployments
testpypi — 416c7d07 Deployed Oct 10, 2026 by Thecave3 via Publish libe3py to TestPyPI #71
pypi — 416c7d07 Deployed Oct 10, 2026 by Thecave3 via Publish libe3py to PyPI #71
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant